home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
FishMarket 1.0
/
FishMarket v1.0.iso
/
fishies
/
476-500
/
disk_497
/
nlcalc
/
source
/
cfunc.c
< prev
next >
Wrap
C/C++ Source or Header
|
1992-05-06
|
16KB
|
549 lines
/*
* CALC Provides a calculator that opens on the active screen when
* you press a specific key sequence. Otherwise, the program
* waits quitely in the background.
*
* Copyright 1989 by Davide P. Cervone.
* You may use this code, provided this copyright notice is kept intact.
*/
#define INTUITION_PREFERENCES_H /* don't need 'em */
#include <intuition/intuition.h>
#include <proto/intuition.h>
#include <proto/dos.h>
#include <proto/graphics.h>
#include <proto/exec.h>
#include "cHandler.h"
#include "cKeys.h"
/*
* ASCII codes for some special keys
*/
#define CTRLC 3
#define BACKSPACE 8
#define ENTER 13
/*
* Delay() time for keypress to invert corresponding gadget
*/
#define KEYPAUSE 4L
/*
* TINYVALUE is the round-off value for the display.
* BIGVALUE is the largest value that can be dispayed or entered.
*/
#define NODECIMAL -1
#define TINYVALUE 0.000000000001
#define BIGVALUE 999999999999.0
/*
* MAXDISPLAY is the number of characters that are in the display.
* MAXSTACK is the size of the pending operation stack (includes
* parenthesies).
*/
#define MAXDISPLAY 13
#define MAXSTACK 20
/*
* Standard character width for Topaz 8-pt font
*/
#define CHARW 8
static struct IntuiText *DisplayIText = &KeyText[KEY_DISP];
static short DisplayPos; /* The position in the DisplayBuffer */
static double DisplayValue; /* The value on the display */
static double DecimalPower; /* The power of ten for next digit */
static int DecimalPlace = NODECIMAL; /* Decimal place for next digit */
static int NoDigit = TRUE; /* TRUE if digits not yet entered */
static int ErrorStatus; /* TRUE if an error occured */
/*
* The codes for each of the pending operations that can appear on the stack
*/
#define OP_NONE 0
#define OP_PLUS 1
#define OP_MINUS 2
#define OP_TIMES 3
#define OP_DIVIDE 4
#define OP_LPAREN 5
#define OP_RPAREN 6
#define OP_EQUAL 7
#define OP_PAREN 8
/*
* The precedence values for each of the stack operations.
*/
static UBYTE Precedence[] = {0,4,4,5,5,6,2,1,3};
static UBYTE Op[MAXSTACK]; /* The pending operation stack */
static double Val[MAXSTACK]; /* The pending value stack */
static short StackTop; /* pointer to top of stack */
/*
* ClearDisplay()
*
* Clears the display buffer and sets all the flags to indicate that we
* are now entering a number from the keypad.
*/
static void ClearDisplay()
{
NoDigit = FALSE;
DecimalPlace = NODECIMAL;
DecimalPower = 0.0;
DisplayValue = 0.0;
DisplayPos = 1;
DisplayBuffer[DisplayPos] = '0';
DisplayBuffer[DisplayPos+1] = 0;
DisplayIText->IText = &DisplayBuffer[1];
DisplayIText->LeftEdge = DISPLAYIW - CHARW;
ErrorStatus = FALSE;
}
/*
* RefreshDisplay()
*
* Refresh the DisplayGadget
*/
static void RefreshDisplay()
{
RefreshGList(&CalcGadget[KEY_DISP],CalcWindow,NULL,1L);
}
/*
* DisplayError()
*
* Put the error message in the display buffer, and set the display
* gadget's left edge so that the message is centered. Refresh the
* display. Set the error status flag, and clear the stack.
*/
static void DisplayError(s)
char *s;
{
ClearDisplay();
NoDigit = TRUE;
strcpy(DisplayIText->IText,s);
DisplayIText->LeftEdge = (DISPLAYIW-strlen(DisplayIText->IText)*CHARW+1) / 2;
RefreshDisplay();
ErrorStatus = TRUE;
StackTop = 0;
}
/*
* AddToDisplay()
*
* Check for buffer overflow. If none, then add character into the display
* buffer and adjust the display position (but don't add non-significant
* zero digits). Refresh the display on the screen and cancel any errors.
*/
static int AddToDisplay(c)
char c;
{
if (DisplayPos == MAXDISPLAY ||
DisplayValue > BIGVALUE || DisplayValue < -BIGVALUE)
{
DisplayError("Overflow");
} else {
if (DisplayValue != 0.0 || DisplayBuffer[DisplayPos] != '0' ||
DecimalPlace != NODECIMAL) DisplayIText->LeftEdge -= CHARW;
DisplayBuffer[DisplayPos++] = c;
DisplayBuffer[DisplayPos] = 0;
if (DisplayValue == 0.0 && c == '0' && DecimalPlace == NODECIMAL)
DisplayPos--;
RefreshDisplay();
ErrorStatus = FALSE;
}
return(ErrorStatus == FALSE);
}
/*
* SetDisplay()
*
* Sets the display to the given value.
* Check for overflow. If none, then
* Set the display value and the current display position.
* If the value is near zero, set the display to zero,
* Otherwise
* If the value is negative, add a minus sign and make the value positive.
* divide the value by the power of ten that makes its integer part only
* one digit and add a small offset so that we round up.
* If the number is less than 1.0, then add '0.' and as many zeros as
* needed to get to the first non-zero decimal digit.
* Mark the last non-zero decimal place.
* As long as the value is still non-zero,
* Find the first digit of the number (TINYVALUE is for round-off errors)
* Multiply by ten to get next digit ready.
* Add a decimal place if it is time.
* Check if the digit is non-zero (or a non-trivial zero)
* Decrement the digits still before the decimal place.
* If the display is filled, cancel the loop.
* NULL-terminate the display string.
* Calculate the display offset, and refresh the display.
* Set the flags.
*/
static void SetDisplay(v)
double v;
{
char c;
short digits,LastNonZero;
extern double pow(),log10();
if (v > BIGVALUE || v < -BIGVALUE)
{
DisplayError("Overflow");
} else {
DisplayValue = v;
DisplayPos = 1;
DisplayIText->IText = &DisplayBuffer[1];
if (v <= TINYVALUE && v >= -TINYVALUE)
{
DisplayBuffer[DisplayPos++] = '0';
DisplayBuffer[DisplayPos++] = '.';
LastNonZero = DisplayPos;
} else {
if (v < 0.0)
{
DisplayBuffer[DisplayPos++] = '-';
v = -v;
}
digits = log10(v);
v = v / pow(10.0,(double)digits) + (5.0*TINYVALUE);
if (digits < 0)
{
DisplayBuffer[DisplayPos++] = '0';
DisplayBuffer[DisplayPos++] = '.';
LastNonZero = DisplayPos;
while (++digits) DisplayBuffer[DisplayPos++] = '0';
digits = -1;
} else {
LastNonZero = DisplayPos;
}
while (v > TINYVALUE)
{
c = v + TINYVALUE; v = (v-c) * 10.0;
DisplayBuffer[DisplayPos++] = c + '0';
if (digits == 0 && DisplayPos <= MAXDISPLAY)
DisplayBuffer[DisplayPos++] = '.';
if (c || digits >= 0) LastNonZero = DisplayPos;
digits--;
if (DisplayPos > MAXDISPLAY) v = 0.0;
}
}
DisplayBuffer[LastNonZero] = 0;
DisplayIText->LeftEdge = DISPLAYIW - (LastNonZero-1)*CHARW;
RefreshDisplay();
ErrorStatus = FALSE;
NoDigit = TRUE;
DecimalPlace = NODECIMAL;
}
}
/*
* ClearEntry()
*
* Clear the display. Set the NoDigit flag to TRUE so that the zero we are
* now displaying will not be part of the displayed number.
*/
static void ClearEntry()
{
ClearDisplay();
NoDigit = TRUE;
RefreshDisplay();
}
/*
* DoFunction()
*
* Do the operation that's on the stack using the value on the top of the
* stack as the first operand and the display value as the second one.
* (Check for division by zero errors)
*/
static void DoFunction()
{
switch(Op[StackTop])
{
case OP_PLUS:
DisplayValue = Val[StackTop] + DisplayValue;
break;
case OP_MINUS:
DisplayValue = Val[StackTop] - DisplayValue;
break;
case OP_TIMES:
DisplayValue = Val[StackTop] * DisplayValue;
break;
case OP_DIVIDE:
if (DisplayValue != 0.0)
DisplayValue = Val[StackTop] / DisplayValue;
else
DisplayError("Zero Division");
break;
}
}
/*
* DoOperation()
*
* Perform any pending operations of higher (or equal precedence) up to
* an open parenthesis (the bottom of the stack is OP_NONE).
* Convert a left-paren to OP_PAREN (the left paren has highest precedence
* so it will not cause other operations to be performed, but when on
* the stack, we want it to have the lowest precedence, so it will not be
* poped until a right paren or an equal.
* If the operation is not an equal or right paren (precedence 1 and 2) then
* Push the operation on the stack (we need to get the next operand)
* Push the current display value onto the stack.
* (If there is a stack overflow, beep the screen).
*/
static void DoOperation(theOp)
int theOp;
{
while (Precedence[theOp] <= Precedence[Op[StackTop]] && theOp != OP_NONE)
{
if (Op[StackTop] == OP_PAREN && theOp != OP_EQUAL)
theOp = OP_NONE;
else
DoFunction();
if (StackTop) StackTop--;
}
if (theOp == OP_LPAREN) theOp = OP_PAREN;
if (Precedence[theOp] > 2)
{
if (StackTop < MAXSTACK-1)
{
Op[++StackTop] = theOp;
Val[StackTop] = DisplayValue;
} else {
DisplayBeep(CalcScreen);
}
}
}
/*
* DoGadget()
*
* The gadget ID indicates which function has been pressed.
* For a dot, if we don't already have a dot, then
* If no other digits have been entered, clear the display value.
* If the current value is zero, add a zero to the display.
* Set the decimal place counters
* Add a dot to the display.
* For one of the function keys, parens, or equal,
* Set NoDigit to indicate a calculated result, and clear the decimal flag.
* Do the operation indicated by the gadget ID.
* If an error did not occur, display the result.
* For the sign change key,
* If the number is a result, show it's negative,
* Otherwise, if the displayalue is not zero, then
* If the displayed value is already negative,
* Change the IText pointer to be past the negative,
* Fix the IText left edge,
* Otherwise
* Change the IText pointer to include the negative,
* Fix the IText left edge,
* Refresh the display.
* For the Root key,
* If the current value is negative, display an error.
* otherwise display the square root.
* For the Percent key,
* If the number has been entered (not caluclated)
* divide by 100
* If the pending operation is + or -, then change display to
* the given percent of the pending operand.
* Display the result.
* For the Clear key,
* Clear the stack of all pending operations,
* and display the a zero.
* For the Clear Entry key, clear the entry.
* For a numeric key (the GadgetID tells which number was pressed),
* If this is the first digit,
* Clear the display area,
* Set the display to the number that was pressed, and
* Add the correct digit to the display.
* Otherwise
* If the value contains a decimal place,
* increase the decimal power of the current digit position,
* and increase the decimal place counter for the display.
* Set the display value to the current value plus the number pressed
* (shifted to the proper decimal place),
* Add the digit to the display.
* Otherwise (no decimal place yet)
* multiply the current value by 10 and add the number pressed
* Add the digit to the display.
*/
void DoGadget(theGadget)
struct Gadget *theGadget;
{
if (theGadget)
{
switch(theGadget->GadgetID)
{
case KEY_DOT:
if (DecimalPlace == NODECIMAL)
{
if (NoDigit || DisplayValue == 0.0)
{
ClearDisplay();
DisplayPos++;
}
DecimalPlace = 0;
DecimalPower = 1.0;
AddToDisplay('.');
}
break;
case KEY_PLUS:
case KEY_MINUS:
case KEY_TIMES:
case KEY_DIVIDE:
case KEY_LPAREN:
case KEY_RPAREN:
case KEY_EQUAL:
NoDigit = TRUE;
DecimalPlace = NODECIMAL;
DoOperation(theGadget->GadgetID - KEY_PLUS + 1);
if (ErrorStatus == FALSE) SetDisplay(DisplayValue);
break;
case KEY_SIGN:
if (NoDigit)
{
SetDisplay(-DisplayValue);
} else if (DisplayValue > TINYVALUE || DisplayValue < -TINYVALUE) {
if (DisplayIText->IText[0] == '-')
{
DisplayIText->IText++;
DisplayIText->LeftEdge += CHARW;
} else {
DisplayIText->IText--;
DisplayIText->LeftEdge -= CHARW;
}
DisplayValue = -DisplayValue;
RefreshDisplay();
}
break;
case KEY_SQRT:
if (DisplayValue < 0.0)
DisplayError("Imaginary");
else
SetDisplay(sqrt(DisplayValue));
break;
case KEY_PERCENT:
if (NoDigit == FALSE)
{
DisplayValue /= 100.0;
if (Op[StackTop] == OP_PLUS || Op[StackTop] == OP_MINUS)
DisplayValue *= Val[StackTop];
}
SetDisplay(DisplayValue);
break;
case KEY_CLEAR:
StackTop = 0;
ClearEntry();
break;
case KEY_DISP:
if (NoDigit == FALSE) ClearEntry();
break;
default:
if (NoDigit)
{
ClearDisplay();
if (AddToDisplay(theGadget->GadgetID+'0'))
DisplayValue = (double)theGadget->GadgetID;
} else {
if (DecimalPlace != NODECIMAL)
{
DecimalPlace++;
DecimalPower *= 10.0;
if (AddToDisplay(theGadget->GadgetID+'0'))
DisplayValue += theGadget->GadgetID / DecimalPower;
} else {
if (AddToDisplay(theGadget->GadgetID+'0'))
DisplayValue = DisplayValue*10.0 + theGadget->GadgetID;
}
}
break;
}
}
}
/*
* DoKey()
*
* Check the key code for special characters:
* CTRL-C means close the calculator (return FALSE).
* ENTER key is converted to the equal sign (ENTER also equals RETURN).
* Look through the gadget list for a gadget that has this character
* in it's UserData field. This is the gadget associated with this key code.
* If one is found, then
* complement the gadget that was "pressed"
* do the gadget's function
* delay for the key-press delay period (so that we can see the gadget
* while it is complemented)
* and un-complement the gadget.
*/
int DoKey(Code)
int Code;
{
struct Gadget *theGadget = &CalcGadget[0];
int x,y,w,h;
if (Code == CTRLC) return(FALSE);
if (Code == BACKSPACE && NoDigit == FALSE) ClearEntry();
if (Code == ENTER) Code = '=';
while (theGadget && Code != (int)theGadget->UserData)
theGadget = theGadget->NextGadget;
if (theGadget)
{
SetDrMd(rp,COMPLEMENT);
x = theGadget->LeftEdge;
y = theGadget->TopEdge;
w = x + theGadget->Width - 1;
h = y + theGadget->Height - 1;
RectFill(rp,x,y,w,h);
DoGadget(theGadget);
Delay(KEYPAUSE);
RectFill(rp,x,y,w,h);
}
return(TRUE);
}